﻿#region 참조 네임스페이스

using System;
using System.ComponentModel;
using System.Threading;

#endregion

namespace Skying.ComponentModel
{
	public sealed class CallbackContext : IDisposable
	{
		#region 변수

		private object _context;

		#endregion

		#region 프로퍼티

		public bool Invalidated
		{
			get
			{
				return this._context == null;
			}
		}

		#endregion

		#region 생성자/소멸자

		public CallbackContext()
		{

		}

		#endregion

		public void Reset()
		{
			this._context = null;
		}

		public Action Bind(Action action)
		{
			// Create the object-context if it doesn't already exist.
			if (this._context == null)
			{
				this._context = new object();
			}

			// Make a (reference) copy of the current object-context; this is necessary because lambda expressions bind to variables, not values
			object boundContext = this._context;

			return () =>
			{
				// Compare the bound object-context to the current object-context; if they differ, then do nothing
				// Use the static object.Equals instead of the instance object.Equals because the current object-context may be null
				if (object.Equals(boundContext, this._context))
				{
					action();
				}
			};
		}

		public Func<T> Bind<T>(Func<T> func)
		{
			// Create the object-context if it doesn't already exist.
			if (this._context == null)
			{
				this._context = new object();
			}

			// Make a (reference) copy of the current object-context; this is necessary because lambda expressions bind to variables, not values
			object boundContext = this._context;

			return () =>
			{
				// Compare the bound object-context to the current object-context; if they differ, then do nothing
				// Use the static object.Equals instead of the instance object.Equals because the current object-context may be null
				if (object.Equals(boundContext, this._context))
				{
					return func();
				}

				return default(T);
			};
		}

		public Action Bind(Action action, ISynchronizeInvoke synchronizingObject)
		{
			// Create the bound delegate
			Action boundAction = this.Bind(action);

			// Return a synchronized wrapper for the bound delegate
			return () =>
			{
				// We synchronously invoke rather than async (BeginInvoke) because it's up to the implementation whether to require EndInvoke
				if (synchronizingObject.InvokeRequired)
				{
					synchronizingObject.Invoke(boundAction, null);
				}
				else
				{
					boundAction();
				}
			};
		}

		public Action Bind(Action action, SynchronizationContext synchronizationContext)
		{
			return this.Bind(action, synchronizationContext, true);
		}

		public Action Bind(Action action, SynchronizationContext synchronizationContext, bool checkSynchronizationContextVerification)
		{
			if (checkSynchronizationContextVerification)
			{
				// Verify that the synchronization context provides synchronization
				SynchronizationContextRegister.Verify(synchronizationContext.GetType(), SynchronizationContextProperties.Synchronized);
			}

			// Create the bound delegate
			Action boundAction = this.Bind(action);

			// Return a synchronized wrapper for the bound delegate
			return () =>
			{
				synchronizationContext.Send((state) => boundAction(), null);
			};
		}

		public Func<T> Bind<T>(Func<T> func, ISynchronizeInvoke synchronizingObject)
		{
			// Create the bound delegate
			Func<T> boundFunc = this.Bind(func);

			// Return a synchronized wrapper for the bound delegate
			return () =>
			{
				if (synchronizingObject.InvokeRequired)
				{
					return (T)synchronizingObject.Invoke(boundFunc, null);
				}
				else
				{
					return boundFunc();
				}
			};
		}

		public Func<T> Bind<T>(Func<T> func, SynchronizationContext synchronizationContext)
		{
			return this.Bind(func, synchronizationContext, true);
		}

		public Func<T> Bind<T>(Func<T> func, SynchronizationContext synchronizationContext, bool checkSynchronizationContextVerification)
		{
			if (checkSynchronizationContextVerification)
			{
				// Verify that the synchronization context provides synchronization
				SynchronizationContextRegister.Verify(synchronizationContext.GetType(), SynchronizationContextProperties.Synchronized);
			}

			// Create the bound delegate
			Func<T> boundFunc = this.Bind(func);

			// Return a synchronized wrapper for the bound delegate
			return () =>
			{
				T retVal = default(T);
				synchronizationContext.Send((state) => retVal = boundFunc(), null);
				return retVal;
			};
		}

		public Action AsyncBind(Action action, SynchronizationContext synchronizationContext)
		{
			return this.AsyncBind(action, synchronizationContext, true);
		}

		public Action AsyncBind(Action action, SynchronizationContext synchronizationContext, bool checkSynchronizationContextVerification)
		{
			if (checkSynchronizationContextVerification)
			{
				// Verify that the synchronization context provides synchronization
				SynchronizationContextRegister.Verify(synchronizationContext.GetType(), SynchronizationContextProperties.Synchronized);
			}

			// Create the bound delegate
			Action boundAction = this.Bind(action);

			// Return a synchronized wrapper for the bound delegate
			return () =>
			{
				synchronizationContext.Send((state) => boundAction(), null);
			};
		}

		public void Dispose()
		{
			this.Reset();
		}
	}
}